summaryrefslogtreecommitdiff
path: root/src/app/(main)/(home)/posts/[slug]/page.tsx
blob: 5960f067acbfd23ba17aa1d0ad7e8c2121b8cde7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {
  PostComments,
  Share,
} from '@/app/(main)/(home)/posts/[slug]/page.client';
import { PostJsonLd } from '@/components/json-ld';
import { RichText } from '@/components/rich-text';
import { Section } from '@/components/section';
import { TagCard } from '@/components/tags/tag-card';
import { createMetadata } from '@/lib/metadata';
import {
  getPostBySlug,
  getAllPostSlugs,
  type BlogPost,
} from '@/lib/payload-posts';
import { cn } from '@/lib/utils';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Balancer from 'react-wrap-balancer';
import { description as homeDescription } from '@/app/(main)/layout.config';

function PostHeader(props: { post: BlogPost }) {
  const { post } = props;

  return (
    <Section className="p-4 lg:p-6">
      <div
        className={cn(
          'flex flex-col items-start justify-center gap-4 py-8 md:gap-6',
          'sm:items-center sm:rounded-lg sm:border sm:bg-muted/70 sm:px-8 sm:py-20 sm:shadow-xs sm:dark:bg-muted'
        )}
      >
        <div className="flex flex-col gap-2 sm:text-center md:gap-4">
          <h1 className="max-w-4xl font-bold text-3xl leading-tight tracking-tight sm:text-4xl sm:leading-tight md:text-5xl md:leading-tight">
            <Balancer>{post.title}</Balancer>
          </h1>
          <p className="mx-auto max-w-4xl">
            <Balancer>{post.description}</Balancer>
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          {post.tags?.map((tag) => (
            <TagCard name={tag} key={tag} className=" border border-border " />
          ))}
        </div>
      </div>
    </Section>
  );
}

function PostContent({ post }: { post: BlogPost }) {
  return (
    <>
      <PostHeader post={post} />

      <Section className="h-full" sectionClassName="flex flex-1">
        <article className="flex min-h-full flex-col lg:flex-row">
          <div className="flex flex-1 flex-col gap-4">
            <RichText
              content={post.content as Record<string, unknown>}
              className="flex-1 px-4"
              enableProse={true}
            />
            <PostComments
              slug={post.slug}
              className="[&_form>div]:!rounded-none rounded-none border-0 border-border/70 border-t border-dashed dark:border-border"
            />
          </div>
          <div className="flex flex-col gap-4 p-4 text-sm lg:sticky lg:top-[4rem] lg:h-[calc(100vh-4rem)] lg:w-[250px] lg:self-start lg:overflow-y-auto lg:border-border/70 lg:border-l lg:border-dashed lg:dark:border-border">
            <div>
              <p className="mb-1 text-fd-muted-foreground">Written by</p>
              <p className="font-medium">{post.author}</p>
            </div>
            <div>
              <p className="mb-1 text-fd-muted-foreground text-sm">Created At</p>
              <p className="font-medium">{post.date.toDateString()}</p>
            </div>
            <div>
              <p className="mb-1 text-fd-muted-foreground text-sm">Updated At</p>
              <p className="font-medium">{post.updatedAt.toDateString()}</p>
            </div>
            <Share url={post.url} />
          </div>
        </article>
      </Section>
      <PostJsonLd post={post} />
    </>
  );
}

export default async function Page(props: {
  params: Promise<{ slug: string }>;
}) {
  const params = await props.params;
  const post = await getPostBySlug(params.slug);

  if (!post) {
    notFound();
  }

  return <PostContent post={post} />;
}

export async function generateMetadata(props: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const params = await props.params;
  const post = await getPostBySlug(params.slug);

  if (!post) {
    return {};
  }

  return createMetadata({
    title: post.title,
    description: post.description || homeDescription,
    openGraph: {
      url: `/posts/${post.slug}`,
      images: post.image ? [{ url: post.image }] : undefined,
    },
    alternates: {
      canonical: `/posts/${post.slug}`,
    },
  });
}

export async function generateStaticParams(): Promise<{ slug: string }[]> {
  const slugs = await getAllPostSlugs();
  return slugs.map((slug) => ({ slug }));
}